package com.thinkfree.enzyme.sign;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class ApiSignUtil {

	private static final Log logger = LogFactory.getLog(ApiSignUtil.class);

	private static final SimpleDateFormat TIMESTAMP_FORMAT =
			new SimpleDateFormat("yyyyMMddHHmmss");
	static {
		TIMESTAMP_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
	}

	private static final int NONCE_BYTE_LENGTH = 8;
	private static final int REQUEST_URL_BUFSIZE = 512;

	static SecureRandom random = null;

	static {
		try {
			random = SecureRandom.getInstance("SHA1PRNG");
			byte[] seed = random.generateSeed(20);
			random.setSeed(seed);
		} catch (NoSuchAlgorithmException e) {
			logger.fatal(e);
		}
	}
	/**
	 * Following two ~Key variables you should put into are generated by Enzyme
	 * API Center. You can always check these values on your API Key Info page.
	 * When you visit to check the values, do not mistake the term 'Signature'
	 * and 'Signature Key'. Signature itself can be taken as the result(or goal)
	 * of the signing action you are going to invoke. Enzyme API Center only
	 * provides the Signature Key value for signing, which must be preserved
	 * undisclosed in safe place.
	 *
	 * <code>
	 * String SIG_KEY = "Your very secret key";
	 * String API_KEY = "Your apikey";
	 * </code>
	 *
	 * <code>
	 * String baseUrl = "http://thinkfree.com/enzyme/api/";
	 * String method = "view"; // request type
	 * String baseSection = ApiSignUtil.getSignedRequestString(method, API_KEY, "hmacsha1", SIG_KEY);
	 * String paramSection = "&repotype=yourservice&repokey=/doc/path.ext&callback=handler";
	 * String url = baseUrl + baseSection + paramSection;
	 * // Now request this url, and you will get what you want!
	 * </code>
	 *
	 * @param method
	 * @param apikey
	 * @param algorithum
	 * @param sigkey
	 * @return
	 * @throws java.security.SignatureException
	 */
	public static String getSignedRequestString(String method, String apikey, String algorithm, String sigkey)
			throws SignatureException {
		StringBuffer sb = new StringBuffer(REQUEST_URL_BUFSIZE);
		sb.append(getToBeSignedData(method, apikey));
		String signature = getSignature(algorithm, sigkey, sb.toString());
		sb.append("&sigalg=");
		sb.append(algorithm);
		sb.append("&sig=");
		sb.append(signature);
		return sb.toString();
	}

	public static String getSignature(String algorithm, String sigkey, String data)
			throws SignatureException {
		String signature;
		try {
			byte[] keyBytes = sigkey.getBytes();
			SecretKeySpec signingKey = new SecretKeySpec(keyBytes, algorithm);
			Mac mac = Mac.getInstance(algorithm);
			mac.init(signingKey);
			byte[] rawHmac = mac.doFinal(data.getBytes());
			byte[] hexBytes = new Hex().encode(rawHmac);
			signature = new String(hexBytes, "US-ASCII");
		} catch (Exception e) {
			throw new SignatureException(e);
		}
		return signature;
	}

	public static StringBuffer getToBeSignedData(String method, String apikey) {
		String ts = generateTimeStamp();
		String nonce = generateNonce();
		return getToBeSignedData(method, apikey, ts, nonce);
	}

	public static StringBuffer getToBeSignedData(String method, String apikey, String ts, String nonce) {
		StringBuffer data = new StringBuffer(REQUEST_URL_BUFSIZE);
		data.append(method);
		data.append("?apikey=");
		data.append(apikey);
		data.append("&ts=");
		data.append(ts);
		data.append("&nonce=");
		data.append(nonce);
		return data;
	}

	public static String generateSecureRandomKey(String algorithm, String data) {
		String result = null;
		String secure = generateTimeStamp() + generateNonce();
		try {
			result = ApiSignUtil.getSignature(algorithm, secure, data);
		} catch (SignatureException e) {
			logger.fatal(e);
		}
		return result;
	}

	public static String generateNonce() {
		byte[] nonce = new byte[NONCE_BYTE_LENGTH];
		random.nextBytes(nonce);
		return new String(new Hex().encode(nonce));
	}

	public static String generateTimeStamp() {
		return TIMESTAMP_FORMAT.format(new Date());
	}

}
